// 数组响应式
// 1、替换数组原型中的7个方法
const orginalProto = Array.prototype;
const arrayProto = Object.create(orginalProto);
['push','pop','shift','unshift','splice','reverse','sort'].forEach(method=>{
  arrayProto[method] = function(){
    // 原始操作
    orginalProto[method].apply(this,arguments)
    // 覆盖操作，通知更新
    console.log(`执行${method}`);
  }
})
// Vue.util.defineReactive(对象,键,值)
// defineReactive执行一次就形成一个闭包，val会保留下来
function defineReactive(obj, key, val) {
  // 递归
  observe(val)
  // 每一次定义响应式属性时，都应该产生一个dep
  const dep = new Dep();
  // 属性拦截
  Object.defineProperty(obj, key, {
    get() {
      console.log('get', key);
      // 依赖收集
      Dep.target && dep.addDep(Dep.target)
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        console.log('set', key);
        // 防止对对象重新赋值为一个新的{}
        observe(newVal)
        val = newVal;
        // 通知dom更新
        dep.notify()
      }
    }
  })
}

// 遍历传入obj的所有属性，执行响应式处理
function observe(obj) {
  if (typeof obj !== 'object' || obj == null) {
    return;
  }
  // 判断传入的是不是数组
  if(Array.isArray(obj)){
    // 覆盖原型，替换变更操作
    obj.__proto__=arrayProto
    // 对内部执行响应式
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
      observe(i)
    }
  }else{
    Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]));
  }
}

// 代理,可以直接使用this访问到data中的属性
function proxy(vm) {
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key];
      },
      set(newVal) {
        vm.$data[key] = newVal;
      }
    })
  })
}

class Vue {
  constructor(options) {
    // 1.保存传参
    this.$options = options;
    this.$data = options.data;
    // 2.对options中的data做响应式
    // 递归遍历data中的对象做响应式处理
    observe(this.$data);
    // 3.代理
    proxy(this)

    // 3.编译模板
    new Compile(options.el,this)
  }
}

// 遍历模板树，解析其中动态部分，初始化并获得更新函数
class Compile{
  constructor(el,vm){
    // 保存实例
    this.$vm = vm
    // 获取宿主元素的dom节点
    this.$el = document.querySelector(el);
    // 编译
    this.compile(this.$el)
  }
  compile(el){
    console.log(el);
    // 遍历el
    const childNode = el.childNodes
    childNode.forEach(node=>{
      if (this.isElement(node)) {
        // dom元素 需要解析动态的指令，属性绑定，事件
        // console.log("编译元素",node.nodeName);
        const attrs = node.attributes
        Array.from(attrs).forEach(attr=>{
          // 判断是否是动态属性
          // 1、指令v-xxxx
          const attrName =attr.name;
          const exp = attr.value
          if (this.isDir(attrName)) {
            const dir = attrName.substring(2)
            // 判断是否是定义好的指令，如果是，则执行对应方法进行解析
            this[dir] && this[dir](node,exp)
          }
          // 事件处理
          if(this.isEvent(attrName)){
            // @click
            const dir = attrName.substring(1)  //click
            // 事件监听
            this.eventHandler(node,exp,dir)
          }
        })

        // 递归
        if (node.childNodes && node.childNodes.length>0) {
          this.compile(node)
        }
      }else if(this.isInter(node)){
        // {{}}表达式
        // console.log("编译插值",node.textContent);
        this.compileText(node)
      }
    })
  }
  // 处理所有动态绑定的值 dir：指令名称
  update(node,exp,dir){
    // 初始化
    const fun = this[dir+'Updater']
    fun && fun(node,this.$vm[exp])
    // 创建watcher实例，负责后续所有的更新操作
    // 闭包，内部函数不会被释放
    new Watcher(this.$vm,exp,function(val){
      fun && fun(node,val)
    })
  }
  // v-text
  text(node, exp){
    this.update(node,exp,'text')
  }
  textUpdater(node,val){
    node.textContent = val;
  }
  // v-html
  html(node,exp){
    this.update(node,exp,'html')
  }
  htmlUpdater(node,val){
    node.innerHTML = val
  }
  
  // 解析{{}}
  compileText(node){
    // 获取表达式的值
    this.update(node,RegExp.$1,'text')
    // node.textContent = this.$vm[RegExp.$1]
  }


  // 判断是否是v-开头
  isDir(attrName){
    return attrName.startsWith('v-')
  }
  isElement(node){
    return node.nodeType === 1
  }
  // {{xxxxx}}
  isInter(node){
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }
  isEvent(dir){
    return dir.indexOf('@')==0
  }
  eventHandler(node,exp,dir){
    const fun = this.$vm.$options.methods && this.$vm.$options.methods[exp];
    node.addEventListener(dir,fun.bind(this.$vm))
  }
  // v-model
  model(node,exp){
    // update方法只完成赋值和更新
    this.update(node,exp,'model')
    // 事件监听
    node.addEventListener('input',e=>{
      this.$vm[exp] = e.target.value
    })
  }
  modelUpdater(node,value){
    // 表单元素赋值
    node.value = value;
  }
}


// 负责具体节点的更新
class Watcher{
  constructor(vm,key,updater){
    this.vm = vm
    this.key = key
    this.updater = updater
    // 读当前值，出发依赖
    Dep.target = this;
    this.vm[this.key]
    Dep.target = null
  }

  update(){
    const val = this.vm[this.key]
    this.updater.call(this.vm,val)
  }
}
// Dep和响应式的属性key之前是一一对应的，负责通知watcher去更新
class Dep{
  // 将watcher和dep简历关系
  constructor(){
    this.deps = []
  }
  addDep(dep){
    this.deps.push(dep)
  }
  notify(){
    this.deps.forEach(dep => {
      dep.update()
    });
  }
}